/*
 * Copyright (c) 1994 NeXT Computer, Inc.  All rights reserved. 
 *
 * HISTORY
 * 4-Mar-94    Rakesh Dubey at NeXT
 *      Created. 
 */

#import "SoundBlaster8.h"
#import "SoundBlaster8Registers.h"

#import <driverkit/generalFuncs.h>

static const char codecDeviceName[] = "SoundBlaster8";
static const char codecDeviceKind[] = "Audio";

static  sbCardParameters_t sbCardType;	 	// hardware type
static  BOOL lowSpeedDMA;		 	// different programming

/*
 * Include inline functions. 
 */
#import "SoundBlaster8Inline.h"

@implementation SoundBlaster8

/*
 * Probe and initialize new instance 
 */
+ (BOOL) probe:deviceDescription
{
    SoundBlaster8       *dev;
    IORange             *portRangeList;
    int                 numPortRanges;
    unsigned int        baseAddress;

#ifdef DEBUG
    int                 i;
#endif DEBUG
 
    dev = [self alloc];
    if (dev == nil)
        return NO;

    portRangeList = [deviceDescription portRangeList];
    numPortRanges = [deviceDescription numPortRanges];
    
    if (numPortRanges < 1)
    	return NO;

#ifdef DEBUG
    for (i=0; i < numPortRanges; i++) {
        IOLog("SoundBlaster8: port %x %d\n",
                portRangeList[i].start, portRangeList[i].size);
    }
#endif DEBUG

    baseAddress = portRangeList[0].start;
#ifdef DEBUG
    IOLog("SoundBlaster8: Base address = 0x%x.\n", baseAddress);
#endif DEBUG


    /*
     * Check base address to verify if this is a legal address.
     */

    if ((baseAddress == SB_BASE_ADDRESS_1) ||
        (baseAddress == SB_BASE_ADDRESS_2) ||
        (baseAddress == SB_BASE_ADDRESS_3) ||
        (baseAddress == SB_BASE_ADDRESS_4) ||
        (baseAddress == SB_BASE_ADDRESS_5) ||
        (baseAddress == SB_BASE_ADDRESS_6))     {
        sbBaseRegisterAddress = baseAddress;
    } else {
        IOLog("SoundBlaster8: Invalid port address 0x%0x.\n", baseAddress);
        [dev free];
        return NO;
    }

    /*
     * Now assign all SB DSP and Mixer registers their addresses.
     */
    assignDSPRegAddresses();
    assignMixerRegAddresses();
    
    return [dev initFromDeviceDescription:deviceDescription] != nil;
}


- (BOOL)reset
{
    unsigned int channel        = [[self deviceDescription] channel];
    unsigned int interrupt      = [[self deviceDescription] interrupt];

    IOReturn ioReturn;

    [self setName:codecDeviceName];
    [self setDeviceKind:codecDeviceKind];

    /*
     * Are user selections valid?
     */
    if (checkSelectedDMAAndIRQ(channel, interrupt) == NO) {
	return NO;
    }
    
    /*
     * Now that all hardware parameters have been assigned and/or verified
     * initialize the hardware.
     */
    [self initializeHardware];

    /*
     * This driver is only for 8-bit Sound Blaster cards. If this is not one
     * of these systems we quit since the test is fully reliable.
     */
    
    switch (sbCardType.version) {
      case SB_CLASSIC:
        sbCardType.name = "Classic";
        break;
      case SB_20:
        sbCardType.name = "2.0";
        break;
      case SB_PRO:
        sbCardType.name = "Pro";
        break;
      case SB_16:
        sbCardType.name = "Pro";
        sbCardType.version = SB_PRO;    /* SB16 will emulate it */
        break;
      default:  {
        IOLog("SoundBlaster8: Hardware not detected at port 0x%0x.\n",
		sbBaseRegisterAddress);
        return NO;
      }
    }
    
    IOLog("SoundBlaster8: Sound Blaster %s (ver %d.%d) at port 0x%0x.\n", 
		 sbCardType.name, 
		 sbCardType.majorVersion, sbCardType.minorVersion, 
		 sbBaseRegisterAddress);
    
    /*
     * Initialize DMA controller.
     */
     
    [self disableChannel: 0];

    /*
     * This call is only applicable in EISA systems. All dma channels
     * that are available to this driver in ISA machines are 8-bit. So we do
     * this setup only for EISA machines. 
     */
    if ([self isEISAPresent]) {
        ioReturn = [self setDMATransferWidth:IO_8Bit forChannel:0];
        if (ioReturn != IO_R_SUCCESS) {
            IOLog("SoundBlaster8: could not set transfer width to 8 bits, error %d.\n", ioReturn);
            return NO;
        }
    }
    
    ioReturn = [self setTransferMode: IO_Single forChannel: 0];
    if (ioReturn != IO_R_SUCCESS)  {
        IOLog("%s: dma transfer mode error %d\n", [self name], ioReturn);
        return NO;
    }

    /*
     * We will program the DMA controller in auto-init mode but the card is
     * in single cycle mode. So at every interrupt we only need to reprogram
     * the card.
     */
    ioReturn = [self setAutoinitialize: YES forChannel: 0];
    if (ioReturn != IO_R_SUCCESS) {
        IOLog("%s: dma auto initialize error %d", [self name], ioReturn);
        return NO;
    }
    
    return YES;
}


- (void) initializeHardware
{
    resetHardware();
}

/*
 * Converts gain (0 - 32768) into hardware supported gain (0 - 7). If the
 * input source is line (not supported now), simply double the gain. 
 */

- (void)updateInputGainLeft
{
    unsigned int gain = [self inputGainLeft];
    unsigned int left  = 0;
    
    if (gain)
        left = ((gain * MAX_INPUT_GAIN_MICROPHONE)/32768);
    else
        left = gain;    // minimum input gain = 0
        
    setInputGain(LEFT_CHANNEL, left);
#ifdef DEBUG
    IOLog("%s: updateInputGainLeft %d based on gain %d\n", [self name],left, gain);
#endif DEBUG
}

/*
 * Converts gain (0 - 32768) into hardware supported gain (0 - 7)
 */

- (void)updateInputGainRight
{
    unsigned int gain = [self inputGainRight];
    unsigned int right = 0;
    
    if (gain)
        right = ((gain * MAX_INPUT_GAIN_MICROPHONE)/32768);
    else
        right = gain;   // minimum input gain = 0
        
    setInputGain(RIGHT_CHANNEL, right);
#ifdef DEBUG
    IOLog("%s: updateInputGainRight %d based on gain %d\n", [self name], right, gain);
#endif DEBUG
}

- (void)updateOutputMute
{
    enableAudioOutput(! [self isOutputMuted]);
}

/*
 * (0) - (-84) needs to be converted to hardware supported (0) - (15)
 */
- (void) updateOutputAttenuationLeft
{
    unsigned int attenuation = [self outputAttenuationLeft] + 84;
    unsigned int left = 0;
    
    left = ((attenuation * MAX_MASTER_OUTPUT_VOLUME)/84);
   
    setOutputAttenuation(LEFT_CHANNEL, left);
    
#ifdef DEBUG
    IOLog("%s: converted la: %d into %d\n", [self name], attenuation, left);
#endif DEBUG
}

/*
 * (0) - (-84) needs to be converted to hardware supported (0) - (15)
 */
- (void) updateOutputAttenuationRight
{
    unsigned int attenuation = [self outputAttenuationRight] + 84;
    unsigned int right = 0;
    
    right = ((attenuation * MAX_MASTER_OUTPUT_VOLUME)/84);
    
    setOutputAttenuation(RIGHT_CHANNEL, right);
   
#ifdef DEBUG
    IOLog("SoundBlaster8: converted ra: %d into %d\n", attenuation, right);
#endif DEBUG
}

/*
 * Program DSP.
 */
- (void)updateSampleRate
{
    unsigned int rate;
    unsigned int mode;
    
    rate = [self sampleRate];
    mode = ([self channelCount] == 2) ? DSP_STEREO_MODE : DSP_MONO_MODE;

    /*
     * Programming sequence depends upon whether we are doing a low speed or
     * high speed transfer. Rather messy, see SB SDK page 12-5.
     */
    if (sbCardType.version == SB_CLASSIC) {
	lowSpeedDMA = YES;
    } else if (sbCardType.version == SB_20) {
	if (currentDMADirection == DMA_DIRECTION_IN)
	    lowSpeedDMA = (rate < SB_20_LOW_SPEED_RECORD) ? YES : NO;
	else
	    lowSpeedDMA = (rate < SB_20_LOW_SPEED_PLAYBACK) ? YES : NO;
    } else if (sbCardType.version == SB_PRO) {
        if (mode == DSP_STEREO_MODE)
	    rate *= 2;
	lowSpeedDMA = (rate < SB_PRO_LOW_SPEED) ? YES : NO;
    }
    
    setCodecDataMode(mode, currentDMADirection);
    setCodecSamplingRate(rate);
}


/*
 * Sets the DMA Counter Load register which decides when the next interrupt
 * will arrive.
 */
- (void) setBufferCount:(int)count
{
    setSampleBufferCounter(count);      
}


- (IOReturn) enableAllInterrupts
{
    enableCodecInterrupts();
    return [super enableAllInterrupts];
}

- (void) disableAllInterrupts
{
   disableCodecInterrupts();
   [super disableAllInterrupts];
}

/*
 * Return NO if the hardware does not support this particular playback/record
 * request. The parameter scheme in soundkit does not fit the anarchy
 * of PC world very well.
 */
- (BOOL)isValidRequest: (BOOL)isRead
{
    unsigned int rate;
    unsigned int mode;
    unsigned int encoding;
    
    rate = [self sampleRate];
    encoding = [self dataEncoding];
    mode = ([self channelCount] == 2) ? DSP_STEREO_MODE : DSP_MONO_MODE;

#ifdef DEBUG
    IOLog("SoundBlaster8: rate: %d ", rate);
    
    if (mode == DSP_MONO_MODE)
        IOLog("dataMode: mono ");
    else if (mode == DSP_STEREO_MODE)
        IOLog("dataMode: stereo ");
    else
        IOLog("dataMode: unknown ");
        
    if (encoding == NX_SoundStreamDataEncoding_Linear16)
        IOLog("dataEncoding: linear 16\n");
    else if (encoding == NX_SoundStreamDataEncoding_Linear8)
        IOLog("dataEncoding: linear 8\n");
    else if (encoding == NX_SoundStreamDataEncoding_Mulaw8)
        IOLog("dataEncoding: mulaw 8\n");
    else if (encoding == NX_SoundStreamDataEncoding_Alaw8)
        IOLog("dataEncoding: Alaw 8\n");
    else
        IOLog("dataEncoding: unknown\n");
#endif DEBUG
	
    if (sbCardType.version == SB_PRO) {
        if ((mode == DSP_STEREO_MODE) &&
            (rate > SB_PRO_LOW_SPEED))
            return NO;          
    }
    
    if (sbCardType.version == SB_20) {
	if (isRead && rate > SB_20_LOW_SPEED_RECORD)
	    return NO;
    }
                
    if (sbCardType.version == SB_CLASSIC) {
	if (isRead && rate > SB_CLASSIC_MAX_SPEED_RECORD)
	    return NO;
	if (!isRead && rate > SB_CLASSIC_MAX_SPEED_PLAYBACK)
	    return NO;
    }
    
    return YES;
}


- (BOOL) startDMAForChannel: (unsigned int) localChannel
        read: (BOOL) isRead
        buffer: (IOEISADMABuffer) buffer
        bufferSizeForInterrupts: (unsigned int) bufferSize
{
    IOReturn ioReturn;
    
#ifdef DEBUG
    IOLog("SoundBlaster8: startDMAForChannel\n");
#endif DEBUG

    isValidRequest = [self isValidRequest:isRead];
    
    interruptTimedOut = NO;
    
    if (isValidRequest == NO)   {
        IOLog("%s: unsupported %s mode.\n", [self name],
                isRead ? "recording" : "playback");
	
	if (isRead)
	    return YES;
	else
	    return NO;
    }
        
    if (isRead)
        currentDMADirection = DMA_DIRECTION_IN;
    else
        currentDMADirection = DMA_DIRECTION_OUT;

    /*
     * Output must be off while recording. 
     */
    if (![self isOutputMuted])
	enableAudioOutput(isRead ? NO : YES);

    [self updateSampleRate];
    
    dmaDescriptorSize = bufferSize;	// used by interrupt handler
            
#ifdef DEBUG
    if (lowSpeedDMA)
        IOLog("SoundBlaster8: starting low speed DMA ");
    else
        IOLog("SoundBlaster8: starting high speed DMA ");
        
    if (isRead)
        IOLog("input.\n");
    else
        IOLog("output.\n");
#endif DEBUG

    ioReturn = [self startDMAForBuffer: buffer channel: localChannel];

    if (ioReturn != IO_R_SUCCESS) {
        IOLog("%s: could not start DMA channel error %d\n",
                [self name], ioReturn);
        return NO;
    }
        
    ioReturn = [self enableChannel: localChannel];
    
    if (ioReturn != IO_R_SUCCESS) {
        IOLog("%s: could not enable DMA channel error %d\n",
                [self name], ioReturn);
        return NO;
    }

    (void) [self enableAllInterrupts];

    /*
     * The order is important here. See SB SDK page 12-8 and 12-13. 
     */
    if (lowSpeedDMA)    {
        if (isRead) {
            startDMA(DMA_DIRECTION_IN);
        } else {
            startDMA(DMA_DIRECTION_OUT);
        }
        [self setBufferCount: dmaDescriptorSize];
    } else {
        [self setBufferCount: dmaDescriptorSize];
        if (isRead) {
            startDMA(DMA_DIRECTION_IN);
        } else {
            startDMA(DMA_DIRECTION_OUT);
        }
    }
    
    return YES;
}

- (void) stopDMAForChannel: (unsigned int) localChannel read: (BOOL) isRead
{
#ifdef DEBUG
    IOLog("SoundBlaster8: stopDMAForChannel\n");
#endif DEBUG

    /*
     * DMA request was denied bacause of lack of hardware support. 
     */
    if (isValidRequest == NO)
        return;
    
    if (isRead) {
        stopDMAInput();
    } else {
        stopDMAOutput();
    }
    
    (void)[self disableAllInterrupts];
    
    /*
     * Disable channel only after disabling capture and playback. 
     */
    [self disableChannel: localChannel];

    if (isRead == NO)
        enableAudioOutput(NO);
	
    /*
     * Reset DSP to stop high speed DMA transfer. This is necessary since the
     * current "DMA block" might be continuing in case the transfer was
     * interrupted. 
     */
    if (lowSpeedDMA == NO) {
	resetDSPQuick();
    }
}

static void clearInterrupts(void)
{
    /*
     * Acknowledge and clear the interrupt.
     */

    inb(sbDataAvailableStatusReg);
}

- (IOAudioInterruptClearFunc) interruptClearFunc
{
    return clearInterrupts;
}

- (void) interruptOccurredForInput: (BOOL *) serviceInput  
                         forOutput: (BOOL *) serviceOutput
{
#ifdef DEBUG
    IOLog("SoundBlaster8: handleHardwareInterrupt\n");
#endif DEBUG
    
    /*
     * Acknowledge and clear the interrupt.
     */

    inb(sbDataAvailableStatusReg);
    
    /*
     * We do not have simultaneous playback and record in SB.
     */
    if (currentDMADirection == DMA_DIRECTION_OUT)
        *serviceOutput = YES;
    else
        *serviceInput = YES;
        
    if (lowSpeedDMA)    {
        if (currentDMADirection == DMA_DIRECTION_IN) {
            startDMA(DMA_DIRECTION_IN);
        } else {
            startDMA(DMA_DIRECTION_OUT);
        }
        [self setBufferCount: dmaDescriptorSize];	/* needed here */
    } else {
        //[self setBufferCount: dmaDescriptorSize]; 	/* but not here */
        if (currentDMADirection == DMA_DIRECTION_IN) {
            startDMA(DMA_DIRECTION_IN);
        } else {
            startDMA(DMA_DIRECTION_OUT);
        }
    }
}

/*
 * This routine will be called if interrupts are not being received. Some
 * cards seem to lock up once in a while. 
 */
- (void) timeoutOccurred
{
#ifdef DEBUG
    IOLog("%s: timeout occurred.\n", [self name]);
#endif DEBUG

    if (interruptTimedOut == NO) {
	resetDSPQuick();
	interruptTimedOut = YES;		// reset only once
    }
}

/*
 * Choose between different input sources.
 */
 
- (void)setAnalogInputSource:(NXSoundParameterTag) val
{
    if (val == NX_SoundDeviceAnalogInputSource_Microphone) {
        setInputLevel(MICROPHONE_LEVEL_INPUT);
    } else if (val == NX_SoundDeviceAnalogInputSource_LineIn) {
        setInputLevel(LINE_LEVEL_INPUT);
    } else {
        setInputLevel(MICROPHONE_LEVEL_INPUT);  // default
    }
}

/*
 * Parameter access.
 */

- (BOOL)acceptsContinuousSamplingRates
{
    return YES;
}

- (void)getSamplingRatesLow:(int *)lowRate
                                         high:(int *)highRate
{
    *lowRate = 4000;
    *highRate = 44100;
}

- (void)getSamplingRates:(int *)rates
                                count:(unsigned int *)numRates
{
    /* Return some supported rates */
    rates[0] = 4000;
    rates[1] = 8000;
    rates[2] = 11025;
    rates[3] = 22050;
    rates[4] = 44100;
    *numRates = 5;
}

- (void)getDataEncodings: (NXSoundParameterTag *)encodings
                                count:(unsigned int *)numEncodings
{
    encodings[0] = NX_SoundStreamDataEncoding_Linear8;
    *numEncodings = 1;
}

- (unsigned int)channelCountLimit
{
    return (sbCardType.version == SB_PRO) ? 2 : 1;      /* stereo and mono */
}

@end

